package co.fitstart.entity.utils;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.demo2do.core.persistence.support.EntityStatus;


/**
 * 
 * @author downpour
 * @author lute
 */
public class Filter {

    private Map<String, Object> parameters = new LinkedHashMap<String, Object>();

    private List<String> sentences = new ArrayList<String>();

    /**
     * The default constructor
     */
    public Filter() {
        
    }

    /**
     * Adds HQL sentence
     * 
     * @param sentence      the HQL sentence
     * @param parameterKey  the key used in SQL like 'name = :key'
     * @param value         the value of parameter
     * @return              current Filter
     */
    public Filter addSentence(String sentence, String parameterKey, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            this.sentences.add(" " + sentence + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Add HQL sentence 
     * @param sentence    the HQL sentence
     * @param parameters  the parameters {@link Map} represented in sentence
     * @return            current filter
     */
    public Filter addSentence(String sentence, Map<String, Object> parameters) {
        
        for (String key : parameters.keySet()) {
            Object value = parameters.get(key);
            if (value == null || !this.checkStatus(value) || (value instanceof String && StringUtils.isBlank((String) value))) {
                return this;
            }
        }
        
        this.sentences.add(" " + sentence + " ");
        this.parameters.putAll(parameters);
        return this;
    }

    /**
     * Adds HQL equal sentence in the form of "key = :value"
     * 
     * @param key    the name of parameter
     * @param value  the value of parameter
     * @return       current Filter
     */
    public Filter addEquals(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("eq", null, key);
            
            this.sentences.add(" " + key + " = :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Adds HQL equal sentence in the form of "alias.key = :value"
     * 
     * @param alias  the alias of query entity
     * @param key    the name of the parameter
     * @param value  the value of the parameter
     * @see          #addEquals(String, Object)
     * @return       current Filter
     */
    public Filter addEquals(String alias, String key, Object value) {
        
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        
        return addEquals(key, value);
    }

    /**
     * Adds HQL like sentence in the form of "key LIKE :likeValue"
     * 
     * @param key    the name of field to be "like" queried
     * @param value  the value of field queried for
     * @return       current Filter
     */
    public Filter addLike(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("like", null, key);
            
            this.sentences.add(" " + key + " LIKE :" + parameterKey + " ");
            this.parameters.put(parameterKey, "%" + value.toString() + "%");
        }
        
        return this;
    }

    /**
     * Adds HQL like sentence in the form of "alias.key LIKE :likeValue"
     * 
     * @param alias  the name of alias of query entity
     * @param key    the name of field to be "like" queried
     * @param value  the value of field queried for
     * @see          #addLike(String, Object)
     * @return       current Filter
     */
    public Filter addLike(String alias, String key, Object value) {
        
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        
        return addLike(key, value);
    }

    /**
     * Adds HQL ' key is null'
     * 
     * @param key
     * @return
     */
    public Filter addIsNull(String key) {
        this.sentences.add(" " + key + " IS NULL ");
        return this;
    }

    /**
     * Adds HQL ' alias.key is null'
     * 
     * @param alias
     * @param key
     * @return
     */
    public Filter addIsNull(String alias, String key) {

        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        
        return addIsNull(key);
    }

    /**
     * Adds HQL ' key is not null'
     * 
     * @param key
     * @return
     */
    public Filter addIsNotNull(String key) {
        this.sentences.add(" " + key + " IS NOT NULL ");
        return this;
    }

    /**
     * Adds HQL ' alias.key is not null'
     * 
     * @param alias
     * @param key
     * @return
     */
    public Filter addIsNotNull(String alias, String key) {

        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        
        return addIsNotNull(key);
    }

    /**
     * Adds HQL date between sentence in the form of "key >=:startDate AND key <=:endDate"
     * 
     * @param key        the name of Date field
     * @param startDate  the start date for query
     * @param endDate    the end date for query
     * @return           current Filter
     */
    public Filter addDateBetween(String key, Date startDate, Date endDate) {
        if(startDate != null) {
            String parameterKey = this.generateParameterKey("StartD", null, key);
            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, startDate);
        }
        if(endDate != null) {
            String parameterKey = this.generateParameterKey("EndD", null, key);
            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, endDate);
        }
        return this;
    }

    /**
     * Adds HQL date between sentence in the form of "alias.key >=:startDate AND alias.key
     * <=:endDate"
     * 
     * @param alias      the name of alias of query entity
     * @param key        the name of Date field
     * @param startDate  the start date for query
     * @param endDate    the end date for query
     * @return           current Filter
     */
    public Filter addDateBetween(String alias, String key, Date startDate, Date endDate) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addDateBetween(key, startDate, endDate);
    }

    /**
     * Adds HQL date time between sentence in the form of "key >=:startDate AND key 
     * <=(:endDate+1)"
     * 
     * @param key        the name of DateTime field
     * @param startDate  the start date time for query
     * @param endDate    the end date time for query
     * @return           current Filter
     */
    public Filter addDateTimeBetween(String key, Date startDate, Date endDate) {
        if(startDate != null) {
            String parameterKey = this.generateParameterKey("StartDT", null, key);
            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, startDate);
        }
        if(endDate != null) {
            String parameterKey = this.generateParameterKey("EndD", null, key);
            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, DateUtils.addDays(endDate, 1));
        }
        return this;
    }

    /**
     * Adds HQL date time between sentence in the form of "alias.key >=:startDate
     * AND alias.key <=(:endDate+1)"
     * 
     * @param alias      the name of alias of query entity
     * @param key        the name of DateTime field
     * @param startDate  the start date for query
     * @param endDate    the end Date for query
     * @return           current Filter
     */
    public Filter addDateTimeBetween(String alias, String key, Date startDate, Date endDate) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addDateTimeBetween(key, startDate, endDate);
    }

    /**
     * Adds HQL greater than sentence in the form of "key > :value"
     * 
     * @param key    the name of field to greater than query
     * @param value  the value of being greater than
     * @return       current Filter
     */
    public Filter addGreaterThan(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("gt", null, key);
            
            this.sentences.add(" " + key + " > :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Adds HQL greater than sentence in the form of "alias.key > :value"
     * 
     * @param alias  the alias of queried entity
     * @param key    the name of field to greater than query
     * @param value  the value of being greater than
     * @see          #addGreaterThan(String, Object)
     * @return       current Filter
     */
    public Filter addGreaterThan(String alias, String key, Object value) {
        if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addGreaterThan(key, value);
    }

    /**
     * Adds HQL greater than or equal to sentence in the form of "key >= :value"
     * 
     * @param key    the name of field to greater than or equal to query
     * @param value  the value of being greater than or equal to
     * @return       current Filter
     */
    public Filter addGreaterThanEqual(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("ge", null, key);
            
            this.sentences.add(" " + key + " >= :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Adds HQL greater than or equal to sentence in the form of "alias.key >= :value"
     * 
     * @param alias  the alias of queried entity
     * @param key    the name of field to greater than or equal to query
     * @param value  the value of being greater than or equal to
     * @see          #addGreaterThanEqual(String, Object)
     * @return       current Filter
     */
    public Filter addGreaterThanEqual(String alias, String key, Object value) {
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addGreaterThanEqual(key, value);
    }

    /**
     * Adds HQL less than sentence in the form of "key < :value"
     * 
     * @param key    the name of field to less than query
     * @param value  the value of being less than
     * @return       current Filter
     */
    public Filter addLessThan(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("lt", null, key);
            
            this.sentences.add(" " + key + " < :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Add HQL less than sentence in the form of "alias.key < :value"
     * 
     * @param alias  the alias of queried entity
     * @param key    the name of field to less than query
     * @param value  the value of being less than
     * @see          #addLessThan(String, Object)
     * @return       current Filter
     */
    public Filter addLessThan(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addLessThan(key, value);
    }

    /**
     * Adds HQL less than or equal to sentence in the form of "key <= :value"
     * 
     * @param key    the name of field to less than or equal to query
     * @param value  the value of being less than or equal to
     * @return       current Filter
     */
    public Filter addLessThanEqual(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("le", null, key);
            
            this.sentences.add(" " + key + " <= :" + parameterKey + " ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Adds HQL less than or equal to sentence in the form of "alias.key <= :value"
     * 
     * @param alias  the alias of queried entity
     * @param key    the name of field to less than or equal to query
     * @param value  the value of being less than or equal to
     * @return       current Filter
     */
    public Filter addLessThanEqual(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addLessThanEqual(key, value);
    }

    /**
     * Adds HQL bit AND equals sentence in the form of "bitand(key, :value)"
     * 
     * @param key    the name of field to bit AND queried
     * @param value  the value of being bit AND equals to
     * @return       current Filter
     */
    public Filter addBitandEquals(String key, Object value) {
        
        if (value != null && this.checkStatus(value)) {
            
            if(value instanceof String && StringUtils.isBlank((String) value)) {
                return this;
            }
            
            String parameterKey = this.generateParameterKey("baeq", null, key);
            
            this.sentences.add(" bitand(" + key + ", :" + parameterKey + ") > 0 ");
            this.parameters.put(parameterKey, value);
        }
        
        return this;
    }

    /**
     * Adds HQL bit AND equals sentence in the form of "bitand(alias.key, :value)"
     * 
     * @param alias  the name of entity to bit AND query
     * @param key    the name of field to bit AND queried
     * @param value  the value of being bit AND equals to
     * @return       current Filter
     */
    public Filter addBitandEquals(String alias, String key, Object value) {
        
         if(StringUtils.isNotEmpty(alias)) {
            key = alias + "." + key;
        }
        return addBitandEquals(key, value);
    }

    /**
     * Checks if value is not null or empty
     * 
     * @param value  the value to be checked
     * @return       {@code true} is value is not null or empty
     */
    private boolean checkStatus(Object value) {
        
        if(value instanceof EntityStatus) {
            EntityStatus entityStatus = (EntityStatus) value;
            return !entityStatus.isEmpty();
        }
        
        return true;
    }

    /**
     * Generates parameter key used in concatenated HQL sentence
     * 
     * @param operation  the operation being processed on the parameter
     * @param key        the name of the parameter
     * @return           the key of the parameter
     */
    private String generateParameterKey(String operation, String alias, String key) {
        
        StringBuffer sb = new StringBuffer(operation);
        
        if(!StringUtils.isEmpty(alias)) {
            sb.append(alias.substring(0, 1).toUpperCase() + alias.substring(1));
        }
        
        String[] temp = StringUtils.split(key, ".");
        for (int i = 0; i < temp.length; i++) {
            sb.append(temp[i].substring(0, 1).toUpperCase() + temp[i].substring(1));
        }
        
        return sb.toString();
        
    }

    /**
     * Gets the WHERE clause of HQL sentence
     * 
     * @return  the WHERE clause of HQL sentence
     */
    public String getWhereSentence() {
        
        if(sentences.isEmpty()) {
            return "";
        }
        
        return " WHERE" + StringUtils.join(sentences, "AND");
    }

    /**
     * Gets the AND clause sentence generated by this Filter
     * 
     * @return  the AND clause sentence of this Filter
     */
    public String getClauseSentence() {
        
        if(sentences.isEmpty()) {
            return "";
        }
        
        return " AND" + StringUtils.join(this.sentences, "AND");
    }

    /**
     * Gets the parameters Map of this Filter
     * 
     * @return the parameters to get
     */
    public Map<String, Object> getParameters() {
        return parameters;
    }

}
